<?php
namespace payment;

use function GuzzleHttp\json_encode;

class Weixin 
{
    /**
     * 接口返回的数据
     */
	public $data = null;
	
	// * 公众号id
	private $appid = 'wx0e62eb8400c4c9cb';
	// appsecret
	private $appsecret = '90256d8514e4955ab0a89098e8325ac1';

	
	// * 商户号
	private $mch_id = '1490781362';
	/**
	 * 商户密钥
	 */
	// private $key = '60e012b0a27a0672a9e9c3bde1f3ce2a';     //沙箱
	private $key = 'a9ucrk7jgoe8j9wuoasrdebptmiilkp2';
	// * @var string 签名方式
	private $sign_type = 'MD5';     // MD5或HMAC-SHA256
	// 设置代理服务器
	private $proxyHost = '0.0.0.0';
	private $proxyPort = '0';
	// cert证书路径
	private $sslCertPath = '';
	private $sslKeyPath = '';

	//curl超时设置
	private $curlTimeout = 6;
	//异步通知地址
	private $notifyUrl = 'http://fengniaoyizhan.net/api/wxpay/notify';

	public function __construct()
	{
		$this->sslCertPath = dirname(__FILE__) . '/cert/apiclient_cert.pem';
		$this->sslKeyPath = dirname(__FILE__) . '/cert/apiclient_key.pem';
	}



    /**
     * 统一下单
     * @param string $good  商品信息
     * @param string $orderNo  订单号
     * @param int $price	金额（单位：元）
     * @param string $openid	用户id
     * @return 下单成功返回 appid & prepay_id，失败返回false
     */
    public function unifiedOrder($good, $orderNo, $price, $openid){
		/**
		 * 统一下单需要传递的参数
		 */
        $config = [
			// 必须的参数
			'appid' 			=> $this->appid,
			'mch_id' 			=> $this->mch_id,
			'nonce_str' 		=> getNonceStr(),
			'body' 				=> $good,
			'out_trade_no' 		=> $orderNo,
			'total_fee' 		=> $price * 100,
			'spbill_create_ip' 	=> $_SERVER['REMOTE_ADDR'],
			'notify_url'		=> $this->notifyUrl,
			'openid'			=> $openid,
			'trade_type'		=> 'JSAPI',
			// 非必需参数
			'time_start'		=> date('YmdHis'),
			'time_expire'		=> date('YmdHis', time() + 600),
		];
		$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
		$res = $this->runApi($config, $url);
		if(isset($res['result_code']) && $res['result_code'] == 'SUCCESS'){
			return [
				'appid'		=> $this->appid,
				'prepay_id'	=> $res['prepay_id'],
			];
		}else{
			return false;
		}
    }

	/**
	 * 查询订单
	 * @param int $orderNo 订单号
	 * @param bool $isWxNo  是否为微信订单号，默认为否
	 * @return bool 返回支付结果
	 */
	public function queryOrder($orderNo, $isWxNo = false){
		/**
		 * 查询订单传递的参数
		 */
        $config = [
			// 必须的参数
			'appid' 		=> $this->appid,
			'mch_id' 		=> $this->mch_id,
			'nonce_str' 	=> getNonceStr(),
			// 非必需参数	
			// 'sign_type'		=> '',
		];
		if($isWxNo) $config['transaction_id'] = $orderNo;
		else $config['out_trade_no'] = $orderNo;
		$url = 'https://api.mch.weixin.qq.com/pay/orderquery';
		$res = $this->runApi($config, $url);
		if(array_key_exists('trade_state', $res) && $res['trade_state'] == 'SUCCESS'){
            return true;
        }else{
            // dump($res);
            return false;
        }
	}

	/**
	 * 关闭订单,
	 * @param string $orderNo 商户订单号
	 * @return bool
	 */
	public function closeOrder($orderNo){
		/**
		 * 查询订单传递的参数
		 */
        $config = [
			// 必须的参数
			'appid' 		=> $this->appid,
			'mch_id' 		=> $this->mch_id,
			'nonce_str' 	=> getNonceStr(),
			'out_trade_no'	=> $orderNo,
		];
		$url = 'https://api.mch.weixin.qq.com/pay/closeorder';
		$res =  $this->runApi($config, $url);
		if(isset($res['result_code']) && $res['result_code'] == 'SUCCESS') return true;
		else{
			// dump($res);
			return false;
		}
	}

	/**
	 * 退款处理
	 */
	public function refund($orderNo, $refundNo, $isWxNo = false){
		// 参数
		$config = [
			// 必须的参数
			'appid' 		=> $this->appid,
			'mch_id' 		=> $this->mch_id,
			'nonce_str' 	=> getNonceStr(),
			'out_refund_no'	=> $refundNo,
			'total_fee' 	=> 100,
			'refund_fee'	=> 99,
		];
		if($isWxNo) $config['transaction_id'] = $orderNo;
		else $config['out_trade_no'] = $orderNo;
	}


	/**
	 * 执行方法，并处理结果
	 * @param array $config 发送的参数数组
	 * @param string $url 	api接口地址
	 * @param boot $useCert 是否使用证书
	 * @return array 微信方return_code成功时返回数组，否则抛出异常
	 */
	public function runApi($config, $url, $useCert = false){
		$config['sign'] = $this->MakeSign($config);
		$xml = $this->ArrayToXml($config);
		$startTimeStamp = getMillisecond();//请求开始时间
		// dump($xml);
		$response = $this->postXmlCurl($xml, $url, $useCert, 6);
		$res = $this->FromXml($response);
		if($res['return_code'] == 'SUCCESS'){
            if($res['result_code'] == 'SUCCESS'){
                return $res;
            }else{
                return ['err_code' => $res['err_code'], 'err_des' => $res['err_code_des']];
            }
        }else{
            throw new \Exception($res['return_msg']);
        }
	}

    
    /**
	 * 生成签名
	 * @param array $config  配置数组
	 * @return string 签名
	 */
	public function MakeSign($config)
	{
		//签名步骤一：按字典序排序参数
		ksort($config);
		$string = $this->ToUrlParams($config);
		//签名步骤二：在string后加入KEY
		$string = $string . "&key=" . $this->key;
		//签名步骤三：MD5加密或者HMAC-SHA256
		if($this->sign_type == "MD5"){
			$string = md5($string);
		} else if($this->sign_type == "HMAC-SHA256") {
			$string = hash_hmac("sha256",$string ,$this->key);
		} else {
			throw new \Exception("签名类型不支持！");
		}
		
		//签名步骤四：所有字符转为大写
		$result = strtoupper($string);
		return $result;
    }
    
    /**
	 * 格式化参数格式化成url参数
     * 
     * @param array $data
     * @return string
	 */
	private function ToUrlParams($data)
	{
		$buff = "";
		foreach ($data as $k => $v)
		{
			if($k != "sign" && $v != "" && !is_array($v)){
				$buff .= $k . "=" . $v . "&";
			}
		}
		
		$buff = trim($buff, "&");
		return $buff;
    }

    /**
     * 数组转xml
     * 
     */    
    private function ArrayToXml($data)
    {
        $xml = '<xml>';
        foreach($data as $k=>$v)
        {
			if(!empty($v)){
				if (is_numeric($v)) {
					$xml .= "<" . $k . ">" . $v . "</" . $k . ">";
				} else {
					$xml .= "<" . $k . "><![CDATA[" . $v . "]]></" . $k . ">";
				}
			}
        }
        $xml .= '</xml>';
        return $xml;
    }

    /**
	 * 
	 * 通过跳转获取用户的openid，跳转流程如下：
	 * 1、设置自己需要调回的url及其其他参数，跳转到微信服务器https://open.weixin.qq.com/connect/oauth2/authorize
	 * 2、微信服务处理完成之后会跳转回用户redirect_uri地址，此时会带上一些参数，如：code
	 * 
	 * @param string $url  回调地址，当前请求地址
	 *
	 * @return 用户的openid
	 */
	public function GetOpenid($url)
	{
		//通过code获得openid
		if (!isset($_GET['code'])){
			//触发微信返回code码
			$baseUrl = urlencode($url);
			$url = $this->_CreateOauthUrlForCode($baseUrl);
			Header("Location: $url");
			exit();
		} else {
			//获取code码，以获取openid
		    $code = $_GET['code'];
			$openid = $this->GetOpenidFromMp($code);
			return $openid;
		}
	}
	
	/**
	 * 
	 * 获取jsapi支付的参数
	 * @param array $UnifiedOrderResult 统一支付接口返回的数据
	 * @throws WxPayException
	 * 
	 * @return json数据，可直接填入js函数作为参数
	 */
	public function GetJsApiParameters($UnifiedOrderResult)
	{
		if(!array_key_exists("appid", $UnifiedOrderResult)
		|| !array_key_exists("prepay_id", $UnifiedOrderResult)
		|| $UnifiedOrderResult['prepay_id'] == "")
		{
			throw new \Exception("参数错误");
		}

		$jsapi = new WxPayJsApiPay();
		$jsapi->SetAppid($UnifiedOrderResult["appid"]);
		$timeStamp = time();
		$jsapi->SetTimeStamp("$timeStamp");
		$jsapi->SetNonceStr(getNonceStr());
		$jsapi->SetPackage("prepay_id=" . $UnifiedOrderResult['prepay_id']);

		$config = new WxPayConfig();
		$jsapi->SetPaySign($jsapi->MakeSign($config));
		$parameters = json_encode($jsapi->GetValues());
		return $parameters;
    }
    
    /**
	 * 以post方式提交xml到对应的接口url
	 * 
	 * @param string $xml  需要post的xml数据
	 * @param string $url  url
	 * @param bool $useCert 是否需要证书，默认不需要
	 * @param int $second   url执行超时时间，默认30s
	 * @throws Exception
	 */
	public function postXmlCurl($xml, $url, $useCert = false, $second = 30)
	{
		$ch = curl_init();
        $curlVersion = curl_version();
		$ua = "WXPaySDK/3.0.9 (".PHP_OS.") PHP/".PHP_VERSION." CURL/".$curlVersion['version']." "
		. $this->mch_id;

		//设置超时
		curl_setopt($ch, CURLOPT_TIMEOUT, $second);

		$proxyHost = $this->proxyHost;
		$proxyPort = $this->proxyPort;
		//如果有配置代理这里就设置代理
		if($proxyHost != "0.0.0.0" && $proxyPort != 0){
			curl_setopt($ch,CURLOPT_PROXY, $proxyHost);
			curl_setopt($ch,CURLOPT_PROXYPORT, $proxyPort);
		}
		curl_setopt($ch,CURLOPT_URL, $url);
		curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
		curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
		curl_setopt($ch,CURLOPT_USERAGENT, $ua); 
		//设置header
		curl_setopt($ch, CURLOPT_HEADER, FALSE);
		//要求结果为字符串且输出到屏幕上
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
	
		if($useCert == true){
			//设置证书
			//使用证书：cert 与 key 分别属于两个.pem文件
			//证书文件请放入服务器的非web目录下
			$sslCertPath = $this->sslCertPath;
			$sslKeyPath  = $this->sslKeyPath;
			curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
			curl_setopt($ch,CURLOPT_SSLCERT, $sslCertPath);
			curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
			curl_setopt($ch,CURLOPT_SSLKEY, $sslKeyPath);
		}
		//post提交方式
		curl_setopt($ch, CURLOPT_POST, TRUE);
		curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
		//运行curl
		$data = curl_exec($ch);
		//返回结果
		if($data){
			curl_close($ch);
			return $data;
		} else { 
			$error = curl_errno($ch);
			curl_close($ch);
			throw new \Exception("curl出错，错误码:$error");
		}
	}
	
	/**
	 * 
	 * 通过code从工作平台获取openid机器access_token
	 * @param string $code 微信跳转回来带上的code
	 * 
	 * @return openid
	 */
	public function GetOpenidFromMp($code)
	{
		$url = $this->__CreateOauthUrlForOpenid($code);

		//初始化curl
		$ch = curl_init();
        $curlVersion = curl_version();
		$ua = "WXPaySDK/3.0.9 (".PHP_OS.") PHP/".PHP_VERSION." CURL/".$curlVersion['version']." "
		. $this->mch_id;

		//设置超时
		curl_setopt($ch, CURLOPT_TIMEOUT, $this->curlTimeout);

		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,FALSE);
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,FALSE);
		curl_setopt($ch, CURLOPT_USERAGENT, $ua);
		curl_setopt($ch, CURLOPT_HEADER, FALSE);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

		$proxyHost = $this->proxyHost;
		$proxyPort = $this->proxyPort;
		if($proxyHost != "0.0.0.0" && $proxyPort != 0){
			curl_setopt($ch,CURLOPT_PROXY, $proxyHost);
			curl_setopt($ch,CURLOPT_PROXYPORT, $proxyPort);
		}
		//运行curl，结果以json形式返回
		$res = curl_exec($ch);
		if($res){
			curl_close($ch);
			//取出openid
			$data = json_decode($res,true);
			$this->data = $data;
			$openid = $data['openid'];
			return $openid;
		} else { 
			$error = curl_errno($ch);
			curl_close($ch);
			throw new \Exception("curl出错，错误码:$error");
		}
    }
	
	/**
	 * 
	 * 获取地址js参数
	 * 
	 * @return 获取共享收货地址js函数需要的参数，json格式可以直接做参数使用
	 */
	public function GetEditAddressParameters()
	{	
		$config = new WxPayConfig();
		$getData = $this->data;
		$data = array();
		$data["appid"] = $config->GetAppId();
		$data["url"] = "http://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
		$time = time();
		$data["timestamp"] = "$time";
		$data["noncestr"] = WxPayApi::getNonceStr();
		$data["accesstoken"] = $getData["access_token"];
		ksort($data);
		$params = $this->ToUrlParams($data);
		$addrSign = sha1($params);
		
		$afterData = array(
			"addrSign" => $addrSign,
			"signType" => "sha1",
			"scope" => "jsapi_address",
			"appId" => $config->GetAppId(),
			"timeStamp" => $data["timestamp"],
			"nonceStr" => $data["noncestr"]
		);
		$parameters = json_encode($afterData);
		return $parameters;
	}
	
	/**
	 * 
	 * 构造获取code的url连接
	 * @param string $redirectUrl 微信服务器回跳的url，需要url编码
	 * 
	 * @return 返回构造好的url
	 */
	private function _CreateOauthUrlForCode($redirectUrl)
	{
		$urlObj["appid"] = $this->appid;
		$urlObj["redirect_uri"] = "$redirectUrl";
		$urlObj["response_type"] = "code";
		$urlObj["scope"] = "snsapi_base";
		$urlObj["state"] = "STATE"."#wechat_redirect";
		$bizString = $this->ToUrlParams($urlObj);
		return "https://open.weixin.qq.com/connect/oauth2/authorize?".$bizString;
	}
	
	/**
	 * 
	 * 构造获取open和access_toke的url地址
	 * @param string $code  微信跳转带回的code
	 * 
	 * @return 请求的url
	 */
	private function __CreateOauthUrlForOpenid($code)
	{
		$urlObj["appid"] = $this->appid;
		$urlObj["secret"] = $this->appsecret;
		$urlObj["code"] = $code;
		$urlObj["grant_type"] = "authorization_code";
		$bizString = $this->ToUrlParams($urlObj);
		return "https://api.weixin.qq.com/sns/oauth2/access_token?".$bizString;
	}

	 /**
     * 错误代码
     * @param  $code       服务器输出的错误代码
     * return string
     */
    public function error_code($code)
    {
        $errList = array(
            'NOAUTH' => '商户未开通此接口权限',
            'NOTENOUGH' => '用户帐号余额不足',
            'ORDERNOTEXIST' => '订单号不存在',
            'ORDERPAID' => '商户订单已支付，无需重复操作',
            'ORDERCLOSED' => '当前订单已关闭，无法支付',
            'SYSTEMERROR' => '系统错误!系统超时',
            'APPID_NOT_EXIST' => '参数中缺少APPID',
            'MCHID_NOT_EXIST' => '参数中缺少MCHID',
            'APPID_MCHID_NOT_MATCH' => 'appid和mch_id不匹配',
            'LACK_PARAMS' => '缺少必要的请求参数',
            'OUT_TRADE_NO_USED' => '同一笔交易不能多次提交',
            'SIGNERROR' => '参数签名结果不正确',
            'XML_FORMAT_ERROR' => 'XML格式错误',
            'REQUIRE_POST_METHOD' => '未使用post传递参数 ',
            'POST_DATA_EMPTY' => 'post数据不能为空',
            'NOT_UTF8' => '未使用指定编码格式',
        );
        if (array_key_exists($code, $errList)) {
            return $errList[$code];
        }
	}
	
	/**
     * 将xml转为array
     * @param string $xml
     * @throws WxPayException
     */
	public function FromXml($xml)
	{	
		if(!$xml){
			throw new \Exception("xml数据异常！");
		}
        //将XML转为array
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $res = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
		return $res;
	}
}